// $Id: DataMapper.cs 10 2008-02-29 15:58:02Z mcartoixa $
/*
 *  Copyright (C) 2007 Noury Solutions
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 */
/*! \file
 * \brief Generic web services oriented implementation of Data Mappers.
 *
 * \author $Author: mcartoixa $
 * \date $Date: 2008-02-29 16:58:02 +0100 (ven., 29 févr. 2008) $
 * \version $Revision: 10 $
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design.Serialization;
using System.Diagnostics;
using System.Reflection;
using System.Web.Services.Protocols;
using Salamanca.DataAccess.Collections;
using Salamanca.DataRules;

namespace Salamanca.DataAccess.Web
{



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Provides data for web services oriented <include file="../doc/Global.doc.xml" path="doc/template[@name='DataMapper']/*" />s events.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public class DataMapperEventArgs<TDomainModel>:
        EventArgs
        where TDomainModel:
            IDomainModel
    {

        /// <summary>Constructor.</summary>
        public DataMapperEventArgs(TDomainModel domainModel)
        {
            DomainModel=domainModel;
        }

        /// <summary>The <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> involved.</summary>
        public TDomainModel DomainModel;
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Ancestor of a web services oriented <include file="../doc/Global.doc.xml" path="doc/template[@name='DataMapper']/*" /></summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public abstract class DataMapper<TDomainModel, TPrimaryKey, TDataTransferObject>:
        Salamanca.DataAccess.DataMapperBase<TDomainModel, TPrimaryKey, TDataTransferObject>
        where TDomainModel:
            DomainModel<TPrimaryKey, TDataTransferObject>
        where TPrimaryKey:
            IPrimaryKey,
            new()
        where TDataTransferObject:
            class, // SOAP serialization does not accept structs...
            IDataTransferObject,
            new()
    {

        /// <summary>Constructor.</summary>
        /// <param name="protocol"></param>
        /// <param name="instanceCreator">Member used to create new instances for the type. May be <c>null</c>, in which case the default constructor for the type will be used.</param>
        /// <param name="cacheManager">A cache manager. Must not be <c>null</c>.</param>
        protected DataMapper(SoapHttpClientProtocol protocol, DomainModelCreator<TDomainModel> instanceCreator, ICacheManager cacheManager):
            base(instanceCreator, cacheManager)
        {
            _Protocol=protocol;
        }


        /// <summary>Finds the <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> associated to the specified key.</summary>
        public override TDomainModel Find(TPrimaryKey key, DataMapperCollection dataMappers)
        {
            return Load<IDomainModelDataTransferObject>(
                GetFinder(key, dataMappers),
                dataMappers
            );
        }


        /// <summary>Refreshes the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance from the database.</summary>
        /// <param name="domainModel">The <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance to be refreshed.</param>
        protected override void Refresh(TDomainModel domainModel)
        {
            IDomainModel dm=(IDomainModel)domainModel;
            IDomainModelDataTransferObject result=GetFinder((TPrimaryKey)dm.Id, dm.DataMappers)();
            SetData(domainModel, result);
        }

        /// <summary>Returns a delegate that finds the data associated to the spcified parameters.</summary>
        protected abstract FindSingleDelegate<IDomainModelDataTransferObject> GetFinder(TPrimaryKey key, DataMapperCollection dataMappers);

        /// <summary>Returns the list of <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instances
        /// returned by the specified web services <paramref name="finder">delegates</paramref>.</summary>
        protected DomainModelKeyedCollection<TDomainModel> Load<TDomainModelDataTransferObject>(FindMultipleDelegate<TDomainModelDataTransferObject> finder, DataMapperCollection dataMappers)
            where TDomainModelDataTransferObject:
                IDomainModelDataTransferObject
        {
            TDomainModelDataTransferObject[] result=finder();
            List<TDomainModel> ret=new List<TDomainModel>();

            foreach (TDomainModelDataTransferObject d in result)
            {
                TDomainModel m=GetDomainModel(d, dataMappers);
                ret.Add(m);
            }

            return new DomainModelKeyedCollection<TDomainModel>(ret);
        }

        /// <summary>Returns the <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance
        /// returned by the specified web services <paramref name="finder">delegate</paramref>.</summary>
        protected TDomainModel Load<TDomainModelDataTransferObject>(FindSingleDelegate<TDomainModelDataTransferObject> finder, DataMapperCollection dataMappers)
            where TDomainModelDataTransferObject:
                IDomainModelDataTransferObject
        {
            IDomainModelDataTransferObject result=finder();
            return GetDomainModel(result, dataMappers);
        }

        /// <summary>Raises the <see cref="Refreshed" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnRefreshed(TDomainModel domainModel)
        {
            if (Refreshed!=null)
                Refreshed(this, new DataMapperEventArgs<TDomainModel>(domainModel));

            domainModel.TriggerRefreshedEvent(new EventArgs());
        }

        /// <summary>Raises the <see cref="Refreshing" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnRefreshing(TDomainModel domainModel)
        {
            if (Refreshing!=null)
                Refreshing(this, new DataMapperEventArgs<TDomainModel>(domainModel));

            domainModel.TriggerRefreshingEvent(new EventArgs());
        }



        /// <summary>Deletes the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
//        /// <param name="data">The <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance to be deleted.</param>
        protected abstract IDomainModelDataTransferObject Delete(IDomainModelDataTransferObject data, bool force);

        /// <summary>Saves the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
//        /// <param name="data">The <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance to be saved.</param>
        protected abstract IDomainModelDataTransferObject Save(IDomainModelDataTransferObject data, bool force);

        /// <summary>Deletes the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        //        /// <param name="domainModel">The <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance to be deleted.</param>
        protected override void Delete(TDomainModel domainModel, IPersistenceContext context)
        {
            Debug.Assert(context!=null);
            if (context==null)
                throw new ArgumentNullException("context");
            Debug.Assert(!((IDomainModel)domainModel).IsNew);
            if (((IDomainModel)domainModel).IsNew)
                throw new InvalidOperationException(Resources.SR.CannotDeleteNewDomainModel);

            domainModel.TriggerDeletingEvent(new PersistenceEventArgs(context));

            if (context.ForceAction || !((IDomainModel)domainModel).IsNew)
            {
                DoDeleting(domainModel, context);

                SetData(domainModel, (DomainModelDataTransferObject<TDataTransferObject>)Delete(GetData((TDomainModel)domainModel, false), context.ForceAction));

                DoDeleted(domainModel, context);
            }

            domainModel.TriggerDeletedEvent(new PersistenceEventArgs(context));
        }



        /// <summary>Saves the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        //        /// <param name="domainModel">The <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance to be saved.</param>
        protected override void Save(TDomainModel domainModel, IPersistenceContext context)
        {
            Debug.Assert(context!=null);
            if (context==null)
                throw new ArgumentNullException("context");

            domainModel.TriggerSavingEvent(new PersistenceEventArgs(context));

            if (context.ForceAction || (domainModel.IsModified && (((IDomainModel)domainModel).IsNew || ((IDomainModel)domainModel).IsLoaded)))
            {
                if (!domainModel.Validate())
                    throw new BrokenRulesException(((IValidatable)domainModel).GetBrokenRules(), domainModel);

                bool isNew=((IDomainModel)domainModel).IsNew;
                if (isNew)
                    DoInserting(domainModel, context);
                else
                    DoUpdating(domainModel, context);

                SetData(domainModel, (DomainModelDataTransferObject<TDataTransferObject>)Save(GetData(domainModel, false), context.ForceAction));

                if (isNew)
                    DoInserted(domainModel, context);
                else
                    DoUpdated(domainModel, context);
            }

            domainModel.TriggerSavedEvent(new PersistenceEventArgs(context));
        }



        private void DoDeleted(TDomainModel domainModel, IPersistenceContext context)
        {
            domainModel.SetNew(true);
            domainModel.SetLoaded(false);

            OnDeleted(domainModel);

            CacheManager.Remove((TPrimaryKey)((IDomainModel)domainModel).Id);
        }

        private void DoInserted(TDomainModel domainModel, IPersistenceContext context)
        {
            domainModel.SetModified(false);
            domainModel.SetNew(false);
            domainModel.SetLoaded(true);

            OnInserted(domainModel);

            CacheManager.Add((TPrimaryKey)((IDomainModel)domainModel).Id, (TDomainModel)domainModel);
        }

        private void DoUpdated(TDomainModel domainModel, IPersistenceContext context)
        {
            domainModel.SetModified(false);

            OnUpdated(domainModel);
        }

        private void DoDeleting(TDomainModel domainModel, IPersistenceContext context)
        {
            OnDeleting(domainModel);
        }

        private void DoInserting(TDomainModel domainModel, IPersistenceContext context)
        {
            OnInserting(domainModel);
        }

        private void DoUpdating(TDomainModel domainModel, IPersistenceContext context)
        {
            OnUpdating(domainModel);
        }



        /// <summary>Raises the <see cref="Deleted" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnDeleted(TDomainModel domainModel)
        {
            if (Deleted!=null)
                Deleted(this, new DataMapperEventArgs<TDomainModel>(domainModel));
        }



        /// <summary>Raises the <see cref="Inserted" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnInserted(TDomainModel domainModel)
        {
            if (Inserted!=null)
                Inserted(this, new DataMapperEventArgs<TDomainModel>(domainModel));
        }



        /// <summary>Raises the <see cref="Updated" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnUpdated(TDomainModel domainModel)
        {
            if (Updated!=null)
                Updated(this, new DataMapperEventArgs<TDomainModel>(domainModel));
        }



        /// <summary>Raises the <see cref="Deleting" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnDeleting(TDomainModel domainModel)
        {
            if (Deleting!=null)
                Deleting(this, new DataMapperEventArgs<TDomainModel>(domainModel));
        }



        /// <summary>Raises the <see cref="Inserting" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnInserting(TDomainModel domainModel)
        {
            if (Inserting!=null)
                Inserting(this, new DataMapperEventArgs<TDomainModel>(domainModel));
        }



        /// <summary>Raises the <see cref="Updating" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnUpdating(TDomainModel domainModel)
        {
            if (Updating!=null)
                Updating(this, new DataMapperEventArgs<TDomainModel>(domainModel));
        }



        /// <summary>Gets the proxy client class.</summary>
        protected SoapHttpClientProtocol Protocol
        {
            get
            {
                return _Protocol;
            }
        }



        /// <summary>Delegate that returns the data of one <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        protected delegate TDomainModelDataTransferObject FindSingleDelegate<TDomainModelDataTransferObject>()
            where TDomainModelDataTransferObject:
                IDomainModelDataTransferObject;
        /// <summary>Delegate that returns the data of several <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instances.</summary>
        protected delegate TDomainModelDataTransferObject[] FindMultipleDelegate<TDomainModelDataTransferObject>()
            where TDomainModelDataTransferObject:
                IDomainModelDataTransferObject;

        private SoapHttpClientProtocol _Protocol;

        /// <summary>Occurs when a <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> has been <see cref="DomainModel{TPrimaryKey,TDataTransferObject}.Refresh">refreshed</see>.</summary>
        public event EventHandler<DataMapperEventArgs<TDomainModel>> Refreshed;
        /// <summary>Occurs when a <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> is about to be <see cref="DomainModel{TPrimaryKey,TDataTransferObject}.Refresh">refreshed</see>.</summary>
        public event EventHandler<DataMapperEventArgs<TDomainModel>> Refreshing;
        /// <summary>Occurs when a <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> has been <see cref="DomainModel{TPrimaryKey,TDataTransferObject}.Delete()">deleted</see>.</summary>
        public event EventHandler<DataMapperEventArgs<TDomainModel>> Deleted;
        /// <summary>Occurs when a <see cref="IDomainModel.IsNew">new</see> <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> has been <see cref="DomainModel{TPrimaryKey,TDataTransferObject}.Save()">saved</see>.</summary>
        public event EventHandler<DataMapperEventArgs<TDomainModel>> Inserted;
        /// <summary>Occurs when a <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> has been <see cref="DomainModel{TPrimaryKey,TDataTransferObject}.Save()">saved</see>.</summary>
        public event EventHandler<DataMapperEventArgs<TDomainModel>> Updated;
        /// <summary>Occurs when a <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> is about to be <see cref="DomainModel{TPrimaryKey,TDataTransferObject}.Delete()">deleted</see>.</summary>
        public event EventHandler<DataMapperEventArgs<TDomainModel>> Deleting;
        /// <summary>Occurs when a <see cref="IDomainModel.IsNew">new</see> <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> is about to be <see cref="DomainModel{TPrimaryKey,TDataTransferObject}.Save()">saved</see>.</summary>
        public event EventHandler<DataMapperEventArgs<TDomainModel>> Inserting;
        /// <summary>Occurs when a <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> is about to be <see cref="DomainModel{TPrimaryKey,TDataTransferObject}.Save()">saved</see>.</summary>
        public event EventHandler<DataMapperEventArgs<TDomainModel>> Updating;

    }
}
